﻿
using System;
using System.IO;
using System.Collections;
using System.Diagnostics;
using System.Reflection;

namespace CCompiler {
    public class CCompiler {
        /* lexical, syntax and semantic errors */
        public ArrayList errorsList;

        public string sourceFile;
        public string intermediateCode = "";
        public string assemblyCode = "";
        public string progName;
        public string assemblerOutput;

        public SymbolTable symbolTable;

        [System.Runtime.InteropServices.DllImport("kernel32.dll")]
        private static extern bool SetEnvironmentVariable(string VariableName, string VariableValue);

        public CCompiler(string sourceFile) {
            this.sourceFile = sourceFile;
            errorsList = new ArrayList();
        }

        public void Run() {
            if (errorsList != null && errorsList.Count == 0)
                Process.Start("cmd", "/k " + progName);
        }

        public void Compile() {
            string input = File.ReadAllText(sourceFile);

            Scanner lexer = new Scanner(input, errorsList);
            Parser parser = new Parser(lexer, errorsList);
            parser.Parse();

            symbolTable = parser.rootSymTab;

            /* getting intermediate code from the parser as text */
            foreach (object instr in parser.instructions) {
                if (instr is string)
                    /* label */
                    intermediateCode += (instr + ":" + "\r\n");
                else
                    /* instruction */
                    intermediateCode += ("\t" + instr.ToString() + "\r\n");
            }

            Debug.Write(intermediateCode);

            if (errorsList.Count > 0)
                return;

            /* generating assembly code */
            CodeGenerator codeGen = new CodeGenerator(parser.instructions, parser.GetStackSize());
            codeGen.GenerateAsm();

            assemblyCode = codeGen.assemblyCode;

            /* extract program file name */
            FileInfo srcFileInfo = new FileInfo(sourceFile);
            string srcDir = srcFileInfo.DirectoryName;
            if (!srcDir.EndsWith("\\"))
                srcDir += "\\";
            progName = srcFileInfo.Name.Remove(srcFileInfo.Name.IndexOf(srcFileInfo.Extension));
            string fullAsmFile = srcDir + progName + ".asm";

            /* write the assembly code to a file */
            File.WriteAllText(fullAsmFile, codeGen.assemblyCode);

            /* delete old masm output files if any */
            DeleteMasmOutputFiles(progName, false, true);

            string output = "";

            /* set the current directory to the source directory */
            Directory.SetCurrentDirectory(srcDir);

            string compDir = new FileInfo(Assembly.GetExecutingAssembly().Location).DirectoryName;

            /* setting environment variables for using MSVC++ assembler and linker */
            SetEnvironmentVariable("INCLUDE", compDir + "\\include");
            SetEnvironmentVariable("LIB", compDir + "\\lib" + ";" + compDir);
            SetEnvironmentVariable("PATH", "C:\\Program Files\\Microsoft Visual Studio 9.0\\VC\\bin" +
                ";" + @"C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE");

            ExecuteProcess("ml", "-Zi -c -Fl -coff " + progName + ".asm", ref output, true);
            assemblerOutput += output;

            ExecuteProcess("link", progName + ".obj irvine32.lib kernel32.lib /SUBSYSTEM:CONSOLE", ref output, true);
            assemblerOutput += output;

            /* delete masm output files */
            DeleteMasmOutputFiles(progName, true, false);
        }

        private int ExecuteProcess(string process, string arguments, ref string output, bool waitForExit) {
            ProcessStartInfo procStartInfo = new ProcessStartInfo(process);

            procStartInfo.Arguments = arguments;
            procStartInfo.CreateNoWindow = true;
            /* necessary for redirecting IO */
            procStartInfo.UseShellExecute = false;
            procStartInfo.RedirectStandardOutput = true;
            procStartInfo.RedirectStandardInput = true;

            /* start the assembler process */
            Process proc = new Process();
            proc.StartInfo = procStartInfo;
            proc.Start();

            /* send new line (enter) for the "Press any key to continue" message */
            //proc.StandardInput.WriteLine();

            /* wait until the process exit */
            if (waitForExit) {
                proc.WaitForExit();
                output = proc.StandardOutput.ReadToEnd();
                return proc.ExitCode;
            }

            return -1;
        }

        private void DeleteMasmOutputFiles(string file /* without file extension */, bool delAsm, bool delExe) {
            /* delete masm output files */
            File.Delete(file + ".ilk");
            File.Delete(file + ".lst");
            File.Delete(file + ".pdb");
            File.Delete(file + ".obj");
            if (delAsm)
                File.Delete(file + ".asm");
            if (delExe)
                File.Delete(file + ".exe");
        }
    }
}